package com.apiauto.data_util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.collections4.map.HashedMap;
import org.apache.http.util.EntityUtils;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

import com.apiauto.dto.CaseDTO;
import com.apiauto.log.Log;
import com.apiauto.pojo.Case;
import com.apiauto.pojo.Rest;
import com.apiauto.pojo.Variable;
import com.apiauto.pojo.WriteData;
import com.apiauto.util.Constant;
import com.google.common.base.Strings;

import cn.hutool.core.bean.BeanUtil;

/**
 * PS:excel工具类
 * 
 * @author lkk
 *
 */
public class ExcelUtil {
	// 接口表字段数组
	public static String[] restFields;
	// 用例表字段数组
	public static String[] caseFields;
	// 变量表字段数组
	public static String[] variableFields;
	// 用例DTO字段数组(需要指定具体需要哪些字段,与dto实体类一一对应)
	public static String[] caseDTOfields = { "ApiNum", "CaseNum", "RequestHeaders", "RequestDataType", "RequestData",
			"CheckPoint", "ExpectResponseResult", "Correlation", "CheckExcuteSql" };

	// key为caseid,value为行号,存储每一个用例的行号
	public static Map<String, Integer> caseIdRowNumMapping = new HashMap<String, Integer>();
	// key为字段名,value为列号,存储每一个字段名的列表
	public static Map<String, Integer> fieldCellNumMapping = new HashMap<String, Integer>();

	// 接口信息集合
	public static List<Rest> restList;
	// 用例信息集合
	public static List<Case> caseList;
	// 变量信息集合
	public static List<Variable> variableList;
	// 变量信息字典
	public static Map<String, String> variableMap = new HashMap<String, String>();
	// 用例实际对象数组
	public static List<CaseDTO> caseDTOList;

	// 写入结果对象集合
	public static List<WriteData> writeDataList = new ArrayList<WriteData>();

	/**
	 * PS:加载excel数据到集合中
	 */

	static {
		restFields = loadExcelNames(Constant.EXCEL_PATH, "接口");
		caseFields = loadExcelNames(Constant.EXCEL_PATH, "用例");
		variableFields = loadExcelNames(Constant.EXCEL_PATH, "变量");

		loadCaseRowNumAndCellNum(Constant.EXCEL_PATH, "用例");

		restList = loadExcelData(Constant.EXCEL_PATH, "接口", Rest.class);
		caseList = loadExcelData(Constant.EXCEL_PATH, "用例", Case.class);
		variableList = loadExcelData(Constant.EXCEL_PATH, "变量", Variable.class);
		variableMap = variableListTransformMap(variableList, variableMap);
		caseDTOList = new ArrayList<CaseDTO>();
		caseDTOList = convertCaseList(caseList, caseDTOList);
	}

	/**
	 * PS:获取指定excel-sheet的数据
	 * 
	 * @param <T>
	 * @param excelPath excel文件路径
	 * @param sheetName 表单名称
	 * @param clazz     最终存储类型的字节码类型
	 * @return excel表单实体对象集合
	 */
	@SuppressWarnings("deprecation")
	public static <T> List<T> loadExcelData(String excelPath, String sheetName, Class<T> clazz) {
		// 判断是否是excel文件
		if (!excelPath.endsWith(".xls") && !excelPath.endsWith(".xlsx")) {
			Log.error("文件不是excel类型");
		}
		// 存储接口实体对象的集合
		List<T> resultList = new ArrayList<T>();
		// 获取工作簿对象
		Workbook workbook = getWorkbook(excelPath);
		// 表对象
		Sheet sheet = workbook.getSheet(sheetName);
		// 获取表的最大行索引
		int maxRowNum = sheet.getLastRowNum();
		// 获取字段提示行对象
		Row fieldRow = sheet.getRow(Constant.FIELDNUM);
		// 获取行对象的最大列数
		int maxCellNum = fieldRow.getLastCellNum();
		// 存储字段提示的数组
		String[] fields = new String[maxCellNum];
		// 遍历行对象获取字段提示塞入field数组
		for (int i = 0; i < maxCellNum; i++) {
			Cell cell = fieldRow.getCell(i, MissingCellPolicy.CREATE_NULL_AS_BLANK);
			String field = cell.getStringCellValue().toString();
			// 截取英文部分
			fields[i] = field.substring(0, field.indexOf("("));
		}
		// 遍历表对象获取数据
		for (int i = Constant.FIELDNUM + 1; i <= maxRowNum; i++) {
			T result = null;
			try {
				// 通过反射获取excel实体类对象
				result = clazz.newInstance();
			} catch (Exception e) {
				Log.error("获取反射对象异常");
			}
			// 获取行对象
			Row row = sheet.getRow(i);
			if (row != null && isEmptyRow(row, maxCellNum)) {
				for (int j = 0; j < maxCellNum; j++) {
					// 获取列对象字符串
					Cell cell = row.getCell(j);
					cell.setCellType(CellType.STRING);
					String value = cell.getStringCellValue();
					// 获取方法名
					String methodString = "set" + fields[j];
					Method method = null;
					try {
						// 反射获取方法对象
						method = clazz.getMethod(methodString, String.class);
						// 调用方法
						method.invoke(result, value);
					} catch (Exception e) {
						Log.error("使用反射对象方法异常");
					}
				}
				resultList.add(result);
			} else {
				continue;
			}

		}
		return resultList;
	}

	/**
	 * PS:判断行对象的所有列对象是否为空
	 * 
	 * @param row        行对象
	 * @param maxCellNum 列数
	 * @return 如果所有列为空返回false,否则返回true
	 */
	@SuppressWarnings("deprecation")
	private static boolean isEmptyRow(Row row, int maxCellNum) {
		// 计数变量
		int nullNum = 0;
		// 循环获取列对象判断是否为空
		for (int i = 0; i < maxCellNum; i++) {
			Cell cell = row.getCell(i, MissingCellPolicy.CREATE_NULL_AS_BLANK);
			cell.setCellType(CellType.STRING);
			String cellString = cell.getStringCellValue();
			if (cellString.isEmpty()) {
				nullNum++;
			}
		}
		if (nullNum == maxCellNum) {
			return false;
		} else {
			return true;
		}

	}

	/**
	 * PS:获取指定excel-sheet的字段名数组
	 * 
	 * @param excelPath excel文件路径
	 * @param sheetName 表单名
	 * @return 字段名数组
	 */
	@SuppressWarnings("deprecation")
	public static String[] loadExcelNames(String excelPath, String sheetName) {
		// 获取工作簿对象
		Workbook workbook = getWorkbook(excelPath);
		// 获取表对象
		Sheet sheet = workbook.getSheet(sheetName);
		// 获取字段行的行对象
		Row fieldRow = sheet.getRow(Constant.FIELDNUM);
		// 获取行对线的最大列数
		int maxFIeldRowNum = fieldRow.getLastCellNum();
		// 最终返回的字段数组
		String[] fields = new String[maxFIeldRowNum];
		// 遍历字段行对象,将字段字符串存入数组
		for (int i = 0; i < maxFIeldRowNum; i++) {
			Cell cell = fieldRow.getCell(i, MissingCellPolicy.CREATE_NULL_AS_BLANK);
			String field = cell.getStringCellValue().toString();
			fields[i] = field.substring(0, field.indexOf("("));
		}
		return fields;

	}

	/**
	 * PS:将casePOJO对象集合转为caseDTO对象集合
	 * 
	 * @param casePOJOList casePOJO集合对象
	 * @param caseDTOList  caseDTO集合对象
	 * @return 转换后的caseDTO集合对象
	 */
	public static List<CaseDTO> convertCaseList(List<Case> casePOJOList, List<CaseDTO> caseDTOList) {
		// 循环遍历casePOJOList,将元素
		for (Case casePOJO : casePOJOList) {
			CaseDTO caseDTO = new CaseDTO();
			// 将casePOJO复制为caseDTO
			BeanUtil.copyProperties(casePOJO, caseDTO);
			caseDTOList.add(caseDTO);
		}
		return caseDTOList;
	}

	/**
	 * PS:加载用例表的字段列索引以及用例编号的行索引
	 * 
	 * @param excelPath 文件路径
	 * @param sheetName 表名
	 */
	public static void loadCaseRowNumAndCellNum(String excelPath, String sheetName) {
		// 获取工作簿对象
		Workbook workbook = getWorkbook(excelPath);
		// 获取工作表对象
		Sheet sheet = workbook.getSheet(sheetName);
		// 获取字段行的行对象
		Row fieldRow = sheet.getRow(Constant.FIELDNUM);
		// 获取行对线的最大列数
		int maxFieldCellNum = fieldRow.getLastCellNum();
		// 最终返回的字段数组
		String[] fields = new String[maxFieldCellNum];
		// 遍历字段行对象,将字段字符串存入数组
		for (int i = 0; i < maxFieldCellNum; i++) {
			// 获取列对象
			Cell cell = fieldRow.getCell(i, MissingCellPolicy.CREATE_NULL_AS_BLANK);
			// 获取列值
			String field = cell.getStringCellValue().toString();
			// 去掉说明部分
			field = field.substring(0, field.indexOf("("));
			// 获取列对象索引
			int cellNum = cell.getAddress().getColumn();
			fieldCellNumMapping.put(field, Integer.valueOf(cellNum));
		}
		// 获取最大行索引
		int maxRowNum = sheet.getLastRowNum();
		for (int i = 2; i <= maxRowNum; i++) {
			// 获取行对象
			Row row = sheet.getRow(i);
			// 获取第一列的列对象
			Cell cell = row.getCell(0);
			// 设置列对象类型
			cell.setCellType(CellType.STRING);
			// 获取字符串类型
			String cellString = cell.getStringCellValue();
			// 获取行索引
			int rowNum = row.getRowNum();
			// 塞入目标集合
			caseIdRowNumMapping.put(cellString, Integer.valueOf(rowNum));
		}

	}

	/**
	 * PS:将结果写入到指定文件路径,表格名称,caseId,字段的文件中
	 * 
	 * @param excelPath 文件路径
	 * @param sheetName 表格名称
	 * @param caseId    用例编号
	 * @param field     字段名
	 * @param result    需要写入的结果
	 */
	public static void writeData(String excelPath, String sheetName, String caseId, String field, String result) {
		// 文件输出流
		OutputStream outputStream = null;
		// 获取工作簿对象
		Workbook workbook = getWorkbook(excelPath);
		// 获取表格对象
		Sheet sheet = workbook.getSheet(sheetName);
		// 获取指定caseId的索引
		int rowNum = caseIdRowNumMapping.get(caseId);
		// 获取指定字段的索引
		int cellNum = fieldCellNumMapping.get(field);
		// 根据索引获取行对象
		Row row = sheet.getRow(rowNum);
		// 根据索引获取列对象
		Cell cell = row.getCell(cellNum);
		// 设置列对象储存类型
		cell.setCellType(CellType.STRING);
		// 设置列对象值
		cell.setCellValue(result);
		try {
			outputStream = new FileOutputStream(new File(excelPath));
			// 写入对应行对象值
			workbook.write(outputStream);
		} catch (FileNotFoundException e) {
			Log.error("未发现文件,文件输出流获取失败");
		} catch (IOException e) {
			Log.error("写入数据异常");
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (IOException e) {
					Log.error("关闭文件输出流失败");
				}
			}
		}
	}

	/**
	 * PS:批量写入数据到excel文件中
	 * 
	 * @param excelPath excel文件路径
	 */
	public static void batchWriteData(String excelPath) {
		// 写入数据到文件的输出流
		OutputStream outputStream = null;
		// 获取工作簿对象
		Workbook workbook = getWorkbook(excelPath);
		// 遍历写入数据集合中的写入数据对象
		for (WriteData writeData : writeDataList) {
			// 从写入对象中获取表名
			String sheetName = writeData.getSheetName();
			// 工作簿对象根据表名获取把表单对象
			Sheet sheet = workbook.getSheet(sheetName);
			// 从写入对象中获取用例编号,在通过用例编号以及编号索引集合获取行索引
			int rowNum = caseIdRowNumMapping.get(writeData.getCaseId());
			// 通过行索引获取行对象
			Row row = sheet.getRow(rowNum);
			// 从写入对象获取字段名.在通过字段名以及字段名索引集合获取列索引
			int cellNum = fieldCellNumMapping.get(writeData.getField());
			// 通过列索引获取列对象
			Cell cell = row.getCell(cellNum);
			// 设置列对象存储数据类型
			cell.setCellType(CellType.STRING);
			// 列对象存储数据
			cell.setCellValue(writeData.getResult());
			// 为文件输出流赋值对象
			try {
				outputStream = new FileOutputStream(new File(excelPath));
			} catch (FileNotFoundException e1) {
				Log.error("-----写入数据时未发现文件-----");
			}
			// 写入数据到工作簿中
			try {
				workbook.write(outputStream);
			} catch (IOException e) {
				Log.error("-----写入数据时发生IO异常-----");
			}
		}
	}

	/**
	 * PS:将指定变量集合转为变量字典
	 * 
	 * @param variableList 变量集合
	 * @param variableMap  变量字典
	 * @return 存入值得变量字典
	 */
	public static Map<String, String> variableListTransformMap(List<Variable> variableList,
			Map<String, String> variableMap) {
		// 遍历集合的变量对象,将key,value存入字典
		for (Variable variable : variableList) {
			String key = variable.getKey();
			String value = variable.getValue();
			variableMap.put(key, value);
		}
		return variableMap;
	}

	/**
	 * PS:根据excel路径获取工作簿对象
	 * 
	 * @param excelPath 文件路径
	 * @return 工作簿对象
	 */
	public static Workbook getWorkbook(String excelPath) {
		// 读取数据的文件输入流对象引用
		FileInputStream fis = null;
		// 最终返回的工作簿的对象引用
		Workbook workbook = null;
		try {
			// 为文件输入流对象引用赋值
			fis = new FileInputStream(new File(excelPath));
			// 获取工作簿对象
			workbook = WorkbookFactory.create(fis);
		} catch (EncryptedDocumentException | IOException e) {
			Log.error("-----获取工作簿对象异常-----");
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					Log.error("-----读取excel工作簿对象时,关闭文件异常-----");
				}
			}
		}
		return workbook;
	}
}
